editor: Align selections by display position instead of byte column - #61997
Conversation
`align_selections` padded rows based on the buffer column, which counts bytes. A row with a multi-byte character before the cursor reported a larger column than the position the cursor is drawn at, so it received the wrong number of spaces. Measure each cursor by its x offset in the laid-out display row and convert the difference to whole spaces using the advance width of a space. Because the display map has already expanded tabs by then, a leading tab now counts as its expanded width rather than as one byte.
SomeoneToIgnore
left a comment
There was a problem hiding this comment.
Thank you.
Had to rewrite the fix as it broke a lot:
-
Measured x per display row (
x_for_display_point), so soft wrap reset the origin at each wrap boundary: junk spaces went into the wrapped line and the line needing padding was left alone — regressing pure-ASCII + soft-wrap cases that worked before (now covered bytest_align_selections_with_soft_wrap) -
Same display-space measurement counted inlay hints and folds, so permanent spaces would be inserted to compensate for ephemeral decorations
-
Rewrote the measurement to content space: tab-expanded buffer-line prefix laid out via
layout_line— wrap/inlay/fold-independent, still fixes multi-byte, tabs, and double-width glyphs; space-advance quantization kept as-is -
As usual, LLM comments are bad, the original one instantly got stale and removed
Objective
Fixes #60192
Closes #62308
editor: align selectionlines cursors up by their buffer column, and that column counts bytes. If a multi-byte character sits before the cursor, the byte column is larger than the position the cursor is actually drawn at, so the row gets padded with the wrong number of spaces.The issue reports it with
←(3 bytes) andπ(2 bytes):Put a cursor on each
#, run the action, and the result is still misaligned:This is not the columnar selection bug fixed in #57097. That one was
select_columnsinselection.rs, where the output is a selection range. This one isalign_selectionsineditor.rs, where the output is inserted spaces, so the same byte-column assumption was left behind in a second place, and fixing it here needs a rounding step that the first fix did not.Solution
Measure each cursor by its x offset in the laid-out display row (
DisplaySnapshot::x_for_display_point), take the target for a column as the furthest x across the rows, then turn the difference into whole spaces by dividing by the advance width of' '. The offset that carries into later columns becomes an x offset instead of a column count.The display map has already expanded tabs by the time the row is laid out, so a leading tab now counts as its expanded width instead of as a single byte.
Two things I would look at first in review:
inf, which saturates to a hugeu32and then tries to allocate that many spaces.I did not add any public items and did not touch
selection.rs.Testing
cargo test -p editor alignon Windows: 6 passed, 0 failed. That is the new test plus the two existingalign_selectionstests, which I did not change and which still pass.test_align_selections_with_multibyte_charscovers the repro from the issue, a second column whose offset has to carry past a multi-byte character in the first, a leading tab, a non-BMP character, and a case where multi-byte characters sit after the cursors and nothing should move.I also checked that the test catches the bug rather than just passing: reverting the change in
editor.rsand keeping the test makes it fail on the repro, inserting four spaces where three are right. Putting the change back makes it pass. The two older align tests pass either way, since they are pure ASCII.What I have not covered:
gpui::testgives every BMP character the same advance, so a test there would assert the test double's behavior rather than the real renderer's.To try it: paste the two lines from the issue, put a cursor on each
#witheditor: select next, then runeditor: align selection. The two#should line up.Self-Review Checklist:
Release Notes:
editor: align selectionsmisaligning rows and Vimctrl-d/ctrl-u/ctrl-fleaving the cursor behind on lines with multi-byte characters or tabs.